package com.gzx.plugin.tjzy.modular.call.pre.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.gzx.plugin.tjzy.modular.call.ht.entity.GzxHtCallRecord;
import com.gzx.plugin.tjzy.modular.call.ht.mapper.GzxHtCallRecordMapper;
import com.gzx.plugin.tjzy.modular.call.pre.bo.GzxPreOutCallAddBo;
import com.gzx.plugin.tjzy.modular.call.pre.bo.GzxPreOutCallDeleteBo;
import com.gzx.plugin.tjzy.modular.call.pre.bo.GzxPreOutCallQueryBo;
import com.gzx.plugin.tjzy.modular.call.pre.bo.GzxPreOutSaveBo;
import com.gzx.plugin.tjzy.modular.call.pre.entity.GzxPreOutCall;
import com.gzx.plugin.tjzy.modular.call.pre.entity.GzxPreOutCallDetail;
import com.gzx.plugin.tjzy.modular.call.pre.enums.GzxBatchStatusEnum;
import com.gzx.plugin.tjzy.modular.call.pre.mapper.GzxPreOutCallDetailMapper;
import com.gzx.plugin.tjzy.modular.call.pre.mapper.GzxPreOutCallMapper;
import com.gzx.plugin.tjzy.modular.call.pre.service.GzxPreOutCallService;
import com.gzx.plugin.tjzy.modular.call.pre.template.HtPreCallTemplateMethod;
import com.gzx.plugin.tjzy.modular.call.pre.vo.GzxPreCallBusinessVo;
import com.gzx.plugin.tjzy.modular.call.pre.vo.GzxPreOutCallDetailVo;
import com.gzx.plugin.tjzy.modular.call.pre.vo.GzxPreOutCallVo;
import com.gzx.plugin.tjzy.modular.complaint.entity.bo.GzxComplaintUserQueryBo;
import com.gzx.plugin.tjzy.modular.complaint.entity.vo.GzxComplaintUserVo;
import com.gzx.plugin.tjzy.modular.complaint.service.GzxComplaintUserService;
import com.gzx.plugin.tjzy.modular.gzxcase.entity.CaseQueryParam;
import com.gzx.plugin.tjzy.modular.gzxcase.entity.GzxCase;
import com.gzx.plugin.tjzy.modular.gzxcase.entity.GzxCaseDetail;
import com.gzx.plugin.tjzy.modular.gzxcase.service.GzxCaseDbService;
import com.gzx.plugin.tjzy.modular.liaison.entity.GzxDebtorLiaison;
import com.gzx.plugin.tjzy.modular.liaison.service.GzxDebtorLiaisonService;
import com.gzx.plugin.tjzy.modular.phone.filter.entity.GzxPhoneFilter;
import com.gzx.plugin.tjzy.modular.phone.filter.service.GzxPhoneFilterService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
import vip.xiaonuo.common.cache.CommonCacheOperator;
import vip.xiaonuo.common.consts.BatchNumberConstant;
import vip.xiaonuo.common.consts.IntConstant;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.pojo.CommonResult;
import vip.xiaonuo.common.tenant.TenantContext;
import vip.xiaonuo.common.util.*;
import vip.xiaonuo.sys.api.SysUserApi;
import vip.xiaonuo.sys.feign.SysUserFeign;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zjt
 * @description
 * @date 2024/12/5 18:33
 */
@Slf4j
@Service
@DS("tjzy-async")
@RequiredArgsConstructor
public class GzxPreOutCallServiceImpl extends HtPreCallTemplateMethod implements GzxPreOutCallService {

    private final GzxPreOutCallMapper preOutCallMapper;

    private final GzxPreOutCallDetailMapper preOutCallDetailMapper;

    private final GzxHtCallRecordMapper htCallRecordMapper;

    private final GzxCaseDbService caseDbService;

    private final GzxDebtorLiaisonService debtorLiaisonService;

    private final GzxPhoneFilterService phoneFilterService;

    private final CommonCacheOperator cacheOperator;

    private final SysUserApi userApi;

    @Value("${pre.out.count}")
    private Integer preOutCallCount;

    private final RedissonClient redissonClient;

    private final GzxComplaintUserService complaintUserService;

    private final SysUserFeign userFeign;

    @Override
    public String deletePreOutCall(GzxPreOutCallDeleteBo bo) {
        //批次状态0待开始1进行中2终止/暂停3全部完成
        try {
            GzxPreOutCall preOutCall = preOutCallMapper.selectOne(Wrappers.<GzxPreOutCall>lambdaQuery()
                    .eq(GzxPreOutCall::getBatchNumber, bo.getBatchNumber())
                    .in(GzxPreOutCall::getBatchStatus, 0, 4));
            if (preOutCall == null) {
                log.info("无权删除他人单据 - 当前批次状态不符合任务删除要求 {} ", bo);
                throw new CommonException("当前批次状态不符合任务删除要求");
            }
            //校验删除逻辑
            SaBaseLoginUser user = StpLoginUserUtil.getLoginUser();
            List<String> roleList = user.getRoleCodeList();
            log.info("无权删除他人单据-当前登录人角色 {} ", roleList);

            //此功能必须给用户授权 “分公司主管” 角色
            if (user.getId().equals(bo.getCreateUser())) {
                //自己：当前条数据的createUser == 当前登录人的 用户id
                deletePreOut(bo.getBatchNumber());
            } else if (user.getOrgId().equals(bo.getCreateDept()) && roleList.contains("branchManager")) {
                //主管：当前登录人的角色是主管 && 当前条数据的 createDept == 当前登录人的 createDept
                deletePreOut(bo.getBatchNumber());
            } else {
                throw new CommonException("无权删除他人单据");
            }
        } catch (Exception e) {
            log.error("无权删除他人单据 {} - 参数 {} ", e, bo);
            throw new CommonException("无权删除他人单据");
        }
        return CommonResult.ok().getMsg();
    }

    /**
     * 删除预测外呼
     */
    public void deletePreOut(String batchNumber) {
        //根据批次号修改主表delete_flag
        preOutCallMapper.delete(new LambdaUpdateWrapper<GzxPreOutCall>().set(GzxPreOutCall::getDeleteFlag, "DELETE")
                .eq(GzxPreOutCall::getBatchNumber, batchNumber));

        //根据批次号删除子表delete_flag
        preOutCallDetailMapper.delete(new LambdaUpdateWrapper<GzxPreOutCallDetail>().set(GzxPreOutCallDetail::getDeleteFlag, "DELETE")
                .eq(GzxPreOutCallDetail::getBatchNumber, batchNumber));
    }

    /**
     * 预测外呼导入
     *
     * @param preOutCallAddBo
     * @param multipartFile
     * @return
     * @throws IOException
     */
    @Override
    @DSTransactional(rollbackFor = Exception.class)
    public String agentImportExcel(GzxPreOutCallAddBo preOutCallAddBo, MultipartFile multipartFile) throws IOException {
        List<GzxPreOutCallDetailVo> preOutCallDetailVoList = null;
        try {
            preOutCallDetailVoList = excelDeduplicateByLiaisonName(multipartFile);
        } catch (IOException e) {
            log.error("Excel解析失败", e);
            throw new CommonException("Excel解析失败");
        }
        GzxPreOutSaveBo preOutSaveBo = buildImport(preOutCallAddBo, preOutCallDetailVoList, null);
        return insertPreOutCall(preOutSaveBo);
    }

    /**
     * 预测外呼数据主管导入
     *
     * @param preOutCallAddBo
     * @param multipartFile
     * @return
     * @throws IOException
     */
    @Override
    @DSTransactional(rollbackFor = Exception.class)
    public String adminImportExcel(GzxPreOutCallAddBo preOutCallAddBo, MultipartFile multipartFile) {
        List<GzxPreOutCallDetailVo> preOutCallDetailVoList = null;
        try {
            preOutCallDetailVoList = excelDeduplicateByLiaisonName(multipartFile);
        } catch (IOException e) {
            log.error("Excel解析失败", e);
            throw new CommonException("Excel解析失败");
        }
        Set<String> accountList = preOutCallDetailVoList.stream().map(GzxPreOutCallDetailVo::getMediatorName).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<String, JSONObject> userMap = new HashMap<>();
        if (CollectionUtil.isNotEmpty(accountList)) {
            List<JSONObject> userList = userApi.getUserByAccount(accountList);
            if (CollectionUtil.isEmpty(userList)) {
                throw new CommonException(accountList + "不存在");
            }
            userMap = userList.stream()
                    .collect(Collectors.toMap(
                            user -> user.getStr("account"),
                            user -> user,
                            (existing, replacement) -> existing));
        }
        Map<String, List<GzxPreOutCallDetailVo>> preOutCallListMap = preOutCallDetailVoList.stream().collect(Collectors.groupingBy(GzxPreOutCallDetailVo::getMediatorName));
        StringBuilder batchMsg = new StringBuilder();
        for (Map.Entry<String, List<GzxPreOutCallDetailVo>> entry : preOutCallListMap.entrySet()) {
            GzxPreOutSaveBo preOutSaveBo = buildImport(preOutCallAddBo, entry.getValue(), userMap.get(entry.getKey()));
            batchMsg.append(insertPreOutCall(preOutSaveBo));
        }
        return batchMsg.toString();
    }

    /**
     * 分页查询预测外呼数据
     *
     * @param preOutCallQueryBo
     * @return
     */
    @Override
    public Page<GzxPreOutCallVo> pageList(GzxPreOutCallQueryBo preOutCallQueryBo) {
        StringBuilder sb = new StringBuilder();
        List<String> userIdList;
        if (StringUtils.isNotEmpty(preOutCallQueryBo.getDirectorId())) {
            sb.append(preOutCallQueryBo.getDirectorId()).append(",");
            //根据主管id获取主管下的所有人
            userIdList = userFeign.getUserIdByDirectorId(preOutCallQueryBo.getDirectorId());
            for (String userId : userIdList) {
                sb.append(userId).append(",");
            }
            sb.deleteCharAt(sb.length() - 1);
            log.info("主管和下面的所有人 {} ", sb);
        }
        return preOutCallMapper.selectVoPage(new Page<>(preOutCallQueryBo.getCurrent(), preOutCallQueryBo.getSize()),
                Wrappers.<GzxPreOutCall>lambdaQuery()
                        .eq(StringUtils.isNotBlank(preOutCallQueryBo.getCreateUserName()), GzxPreOutCall::getCreateName, preOutCallQueryBo.getCreateUserName())
                        .eq(preOutCallQueryBo.getCreateDept() != null, GzxPreOutCall::getCreateDept, preOutCallQueryBo.getCreateDept())
                        .eq(preOutCallQueryBo.getBatchStatus() != null, GzxPreOutCall::getBatchStatus, preOutCallQueryBo.getBatchStatus())
                        //根据主管id获取主管下的所有人
                        .in(StringUtils.isNotEmpty(preOutCallQueryBo.getDirectorId()), GzxPreOutCall::getCreateUser, sb.toString())
                        //新增taskId查询
                        .eq(StringUtils.isNotBlank(preOutCallQueryBo.getTaskId()), GzxPreOutCall::getTaskId, preOutCallQueryBo.getTaskId())
                        //预测外呼新增日期区间查询
                        .between(StringUtils.isNotBlank(preOutCallQueryBo.getBeginTime()) && StringUtils.isNotBlank(preOutCallQueryBo.getEndTime()), GzxPreOutCall::getCreateTime, preOutCallQueryBo.getBeginTime(), preOutCallQueryBo.getEndTime())
                        .orderByDesc(GzxPreOutCall::getId));
    }

    /**
     * 获取预测外呼明细
     *
     * @param preOutCallQueryBo
     * @return
     */
    @Override
    public Page<GzxPreOutCallDetailVo> pageDetail(GzxPreOutCallQueryBo preOutCallQueryBo) {
        return preOutCallDetailMapper.selectVoPage(new Page<>(preOutCallQueryBo.getCurrent(), preOutCallQueryBo.getSize()), Wrappers.<GzxPreOutCallDetail>lambdaQuery()
                .eq(GzxPreOutCallDetail::getBatchNumber, preOutCallQueryBo.getBatchNumber())
                .eq(StringUtils.isNotBlank(preOutCallQueryBo.getPhone()), GzxPreOutCallDetail::getLiaisonPhone, CommonCryptogramUtil.doSm1AesEncrypt(preOutCallQueryBo.getPhone()))
                .orderByDesc(GzxPreOutCallDetail::getCallDuration));
    }

    /**
     * 根据明细ID获取预测外呼明细详情
     *
     * @param id
     * @return
     */
    @Override
    public GzxPreOutCallDetailVo getPreOutCallDetailById(Long id) {
        return preOutCallDetailMapper.selectVoById(id);
    }

    /**
     * 获取批次下接听的数量
     *
     * @param batchNumber
     * @return
     */
    @Override
    public int getCallCount(String batchNumber, Integer filterType) {
        if (filterType == 0) {
            return 0;
        }
        Long count = preOutCallDetailMapper.selectCount(Wrappers.<GzxPreOutCallDetail>lambdaQuery()
                .select(GzxPreOutCallDetail::getId)
                .eq(GzxPreOutCallDetail::getBatchNumber, batchNumber)
                .gt(filterType == 1, GzxPreOutCallDetail::getCallDuration, 0)
                .isNotNull(filterType == 2, GzxPreOutCallDetail::getCallDuration));
        return count.intValue();
    }

    /**
     * 开始任务
     *
     * @param batchNumber
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<String> startCallTask(String batchNumber) {
        String catchBatchNumber = cacheOperator.getCatchString(batchNumber);
        if (StringUtils.isNotBlank(catchBatchNumber)) {
            return CommonResult.error("任务已开始，请勿重复点击");
        }
        GzxPreOutCall preOutCall = preOutCallMapper.selectOne(Wrappers.<GzxPreOutCall>lambdaQuery()
                .eq(GzxPreOutCall::getBatchNumber, batchNumber)
                .eq(GzxPreOutCall::getBatchStatus, 0));
        if (preOutCall == null) {
            return CommonResult.error("只能开始一次，请重新上传");
        }
        if (!preOutCall.getCreateUser().equals(StpLoginUserUtil.getLoginUser().getId())) {
            return CommonResult.error("无权限操作他人上传的数据");
        }
        cacheOperator.setCatchString(batchNumber, batchNumber, IntConstant.VALUE_300);
        preOutCall.setBatchStatus(1);
        String taskId = startPreCall(batchNumber, preOutCall.getCallThread(), preOutCall.getCaller());
        if (StringUtils.isBlank(taskId)) {
            return CommonResult.error("开启预测外呼任务失败");
        }
        preOutCall.setTaskId(taskId);
        preOutCallMapper.updateById(preOutCall);
        return CommonResult.ok();
    }

    /**
     * 关闭预测外呼任务
     *
     * @param batchNumber
     */
    @Override
    public CommonResult<String> closeCallTask(String batchNumber) {
        GzxPreOutCall preOutCall = preOutCallMapper.selectOne(Wrappers.<GzxPreOutCall>lambdaQuery()
                .eq(GzxPreOutCall::getBatchNumber, batchNumber)
                .eq(GzxPreOutCall::getBatchStatus, 1));
        if (preOutCall == null) {
            return CommonResult.error("当前批次状态不符合任务作废要求");
        }
        if (!preOutCall.getCreateUser().equals(StpLoginUserUtil.getLoginUser().getId())) {
            return CommonResult.error("无权限操作他人上传的数据");
        }
        preOutCall.setBatchStatus(2);
        preOutCallMapper.updateById(preOutCall);
        switchTask(preOutCall.getTaskId(), 0);
        return CommonResult.ok();
    }

    /**
     * 继续预测外呼任务
     *
     * @param batchNumber
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<String> continueCallTask(String batchNumber) {
        GzxPreOutCall preOutCall = preOutCallMapper.selectOne(Wrappers.<GzxPreOutCall>lambdaQuery()
                .eq(GzxPreOutCall::getBatchNumber, batchNumber));
        if (preOutCall == null) {
            return CommonResult.error(batchNumber + "不存在");
        }
        if (CommonDateUtil.compareDateWithSpecifiedDays(preOutCall.getCreateTime(), -3)) {
            return CommonResult.error("数据已过期，请重新导入");
        }
        if (preOutCall.getBatchStatus() != 2) {
            return CommonResult.error("当前批次状态不符合任务继续要求");
        }
        if (StringUtils.isBlank(preOutCall.getTaskId())) {
            return CommonResult.error(batchNumber + "任务ID为空");
        }
        switchTask(preOutCall.getTaskId(), 1);
        preOutCall.setBatchStatus(1);
        preOutCallMapper.updateById(preOutCall);
        return CommonResult.ok();
    }

    /**
     * 完成预测外呼任务
     */
    @Override
    public void finishCallTask(String batchNumber) {
        //查询今天的预测外呼任务的
        List<GzxPreOutCall> preOutCallList = preOutCallMapper.selectList(Wrappers.<GzxPreOutCall>lambdaQuery()
                .between(StringUtils.isEmpty(batchNumber), GzxPreOutCall::getCreateTime, CommonDateUtil.getOffsetDateStr(-3) + " 00:00:00", CommonDateUtil.getOffsetDateStr(-3) + " 23:59:59")
                .eq(StringUtils.isNotEmpty(batchNumber), GzxPreOutCall::getBatchNumber, batchNumber));
        if (CollectionUtil.isNotEmpty(preOutCallList)) {
            if (StringUtils.isNotEmpty(batchNumber) && !preOutCallList.getFirst().getCreateUser().equals(StpLoginUserUtil.getLoginUser().getId())) {
                throw new CommonException("无权作废他人单据");
            }
            Set<String> taskIdSet = preOutCallList.stream().map(GzxPreOutCall::getTaskId).collect(Collectors.toSet());
            for (String taskId : taskIdSet) {
                switchTask(taskId, 2);
            }
            preOutCallMapper.update(Wrappers.<GzxPreOutCall>lambdaUpdate()
                    .set(GzxPreOutCall::getBatchStatus, GzxBatchStatusEnum.CANCELLED.getCode())
                    .in(GzxPreOutCall::getTaskId, taskIdSet));
        }
    }

    /**
     * 更换主叫
     *
     * @param batchNumber
     */
    @Override
    public void changeCallee(String batchNumber, String caller) {
        GzxPreOutCall preOutCall = preOutCallMapper.selectOne(Wrappers.<GzxPreOutCall>lambdaQuery()
                .eq(GzxPreOutCall::getBatchNumber, batchNumber));
        super.changeCallee(preOutCall.getTaskId(), caller);
    }

    /**
     * 获取预测外呼明细数据
     *
     * @param id
     * @return
     */
    @Override
    public GzxPreCallBusinessVo getPreCallBusinessById(Long id) {
        return MapstructUtils.convert(preOutCallDetailMapper.selectById(id), GzxPreCallBusinessVo.class);
    }

    /**
     * 测试预测外呼参数
     *
     * @param batchNumber
     */
    @Override
    public void testCallParam(String batchNumber) {
        List<GzxPreOutCallDetail> preOutCallDetailList = preOutCallDetailMapper.selectList(Wrappers.<GzxPreOutCallDetail>lambdaQuery()
                .eq(GzxPreOutCallDetail::getBatchNumber, batchNumber));
        setPreBusiness(preOutCallDetailList, StpLoginUserUtil.getLoginUser().getId());
        for (GzxPreOutCallDetail preOutCallDetail : preOutCallDetailList) {
            log.info("{}测试参数：{},{},{}", batchNumber, preOutCallDetail.getCaseId(), preOutCallDetail.getTenantId(), preOutCallDetail.getCustomerInfo());
        }
    }

    /**
     * 更新预测外呼批次信息
     *
     * @param batchNumber
     */
    @Override
    public void updatePreOutBatch(String batchNumber) {
        // 执行更新操作
        preOutCallMapper.update(Wrappers.<GzxPreOutCall>lambdaUpdate()
                .setSql("completed_count = completed_count + 1,batch_status = if(completed_count >= total_count , 3, batch_status),update_time = now()")
                .eq(GzxPreOutCall::getBatchNumber, batchNumber));
    }

    /**
     * 根据预测外呼明细ID获取预测外呼明细
     *
     * @param idList
     * @return
     */
    @Override
    public List<GzxPreOutCallDetail> selectBatchIds(Set<String> idList) {
        return preOutCallDetailMapper.selectBatchIds(idList);
    }

    /**
     * 批量修改预测外呼明细
     *
     * @param preOutCallDetailList
     */
    @Override
    public void updateBatchByDetailId(List<GzxPreOutCallDetail> preOutCallDetailList) {
        if (CollectionUtil.isNotEmpty(preOutCallDetailList)) {
            Lists.partition(preOutCallDetailList, IntConstant.VALUE_350).forEach(preOutCallDetailMapper::updateBatchByDetailId);
        }
    }

    /**
     * 补偿预测外呼数据
     */
    @Override
    public void offsetPreOutDetailJobHandler() {
        try {
            TenantContext.disableTenantFilter();
            //查询超过5分钟没变化的数据
            List<GzxPreOutCall> preOutCallList = preOutCallMapper.selectList(Wrappers.<GzxPreOutCall>lambdaQuery()
                    .in(GzxPreOutCall::getBatchStatus, 1, 2)
                    .le(GzxPreOutCall::getUpdateTime, CommonDateUtil.getTimeBeforeOrAfterMinutes(null, -5)));
            if (CollectionUtil.isNotEmpty(preOutCallList)) {
                //获取taskId，去恒天话单表查
                List<String> taskIdList = preOutCallList.stream().map(GzxPreOutCall::getTaskId).toList();
                List<GzxHtCallRecord> htCallRecordList = htCallRecordMapper.selectList(Wrappers.<GzxHtCallRecord>lambdaQuery()
                        .in(GzxHtCallRecord::getTaskId, taskIdList));
                //根据TaskId统计数量
                Map<String, Integer> countMap = htCallRecordList.stream().collect(Collectors.groupingBy(
                        GzxHtCallRecord::getTaskId,
                        Collectors.summingInt(e -> 1)
                ));
                List<String> batchNumberList = new ArrayList<>();
                for (GzxPreOutCall preOutCall : preOutCallList) {
                    //恒天数量Map存在这个TaskId,并且统计的数量，比目前的数量大，更新主表已完成数量
                    if (countMap.containsKey(preOutCall.getTaskId()) && countMap.get(preOutCall.getTaskId()) > preOutCall.getCompletedCount()) {
                        RLock rLock = null;
                        try {
                            rLock = redissonClient.getLock(preOutCall.getBatchNumber());
                            // 使用tryLock 来防止死锁或锁获取失败
                            // 尝试获取锁，最多等待10秒，最多持有5秒
                            if (rLock.tryLock(10, 5, TimeUnit.SECONDS)) {
                                // 执行更新操作
                                preOutCallMapper.update(Wrappers.<GzxPreOutCall>lambdaUpdate()
                                        .setSql("completed_count = completed_count + " + (countMap.get(preOutCall.getTaskId()) - preOutCall.getCompletedCount()) + ",batch_status = if(completed_count >= total_count , 3, batch_status),update_time = now()")
                                        .eq(GzxPreOutCall::getId, preOutCall.getId()));
                            } else {
                                log.warn("无法获取到锁，batchNumber: {}", preOutCall.getBatchNumber());
                            }
                        } catch (Exception e) {
                            log.error("获取分布式锁异常:", e);
                        } finally {
                            if (rLock != null && rLock.isHeldByCurrentThread()) {
                                try {
                                    rLock.unlock();
                                } catch (Exception e) {
                                    log.error("释放锁异常{}", e.getMessage());
                                }
                            }
                        }
                        batchNumberList.add(preOutCall.getBatchNumber());
                    }
                }
                //恒天话单记录，以预测外呼明细ID为Key转Map
                Map<String, GzxHtCallRecord> htCallRecordMap = htCallRecordList.stream().collect(Collectors.toMap(
                        GzxHtCallRecord::getBusiness,
                        rowData -> rowData,
                        (oldVal, newVal) -> newVal));
                List<GzxPreOutCallDetail> preOutCallDetailList = preOutCallDetailMapper.selectList(Wrappers.<GzxPreOutCallDetail>lambdaQuery()
                        .in(GzxPreOutCallDetail::getBatchNumber, batchNumberList)
                        .isNull(GzxPreOutCallDetail::getCallId));
                List<GzxPreOutCallDetail> savePreOutCallDetailList = new ArrayList<>();
                for (GzxPreOutCallDetail preOutCallDetail : preOutCallDetailList) {
                    if (htCallRecordMap.containsKey(String.valueOf(preOutCallDetail.getId()))) {
                        GzxHtCallRecord htCallRecord = htCallRecordMap.get(String.valueOf(preOutCallDetail.getId()));
                        GzxPreOutCallDetail savePreOutCallDetail = new GzxPreOutCallDetail();
                        BeanUtils.copyProperties(preOutCallDetail, savePreOutCallDetail);
                        preOutCallDetail.setCallId(htCallRecord.getCallId());
                        preOutCallDetail.setRingTime(htCallRecord.getRingTime());
                        preOutCallDetail.setCallDuration(htCallRecord.getCallDuration());
                        preOutCallDetail.setSignal(htCallRecord.getSignal());
                        savePreOutCallDetailList.add(savePreOutCallDetail);
                    }
                }
                updateBatchByDetailId(savePreOutCallDetailList);
            }
        } finally {
            TenantContext.enableTenantFilter();
            TenantContext.clear();
        }
    }

    /**
     * 调整预测外呼主表已完成数量
     */
    @Override
    public void adjustCompleted() {
        try {
            TenantContext.disableTenantFilter();
            //查询今天所有已完成的批次
            List<GzxPreOutCall> preOutCallList = preOutCallMapper.selectList(Wrappers.<GzxPreOutCall>lambdaQuery()
                    .eq(GzxPreOutCall::getBatchStatus, GzxBatchStatusEnum.COMPLETED.getCode()));
            List<GzxPreOutCall> preOutCallList2 = new ArrayList<>();
            List<String> taskIdList = new ArrayList<>();
            for (GzxPreOutCall preOutCall : preOutCallList) {
                if (!preOutCall.getCompletedCount().equals(preOutCall.getTotalCount())) {
                    preOutCallList2.add(preOutCall);
                    taskIdList.add(preOutCall.getTaskId());
                }
            }
            if (CollectionUtil.isNotEmpty(preOutCallList2)) {
                List<GzxHtCallRecord> htCallRecordList = htCallRecordMapper.selectList(Wrappers.<GzxHtCallRecord>lambdaQuery()
                        .select(GzxHtCallRecord::getId, GzxHtCallRecord::getCallId, GzxHtCallRecord::getTaskId)
                        .in(GzxHtCallRecord::getTaskId, taskIdList));
                // 按 taskId 分组，提取 callId
                Map<String, Set<String>> recordCountMap = htCallRecordList.stream()
                        .collect(Collectors.groupingBy(
                                GzxHtCallRecord::getTaskId,
                                Collectors.mapping(GzxHtCallRecord::getCallId, Collectors.toSet())
                        ));
                Date currentTime = new Date();
                for (GzxPreOutCall preOutCall : preOutCallList2) {
                    if (recordCountMap.containsKey(preOutCall.getTaskId())) {
                        RLock rLock = null;
                        try {
                            rLock = redissonClient.getLock(preOutCall.getBatchNumber());
                            // 使用tryLock 来防止死锁或锁获取失败
                            // 尝试获取锁，最多等待10秒，最多持有5秒
                            if (rLock.tryLock(10, 5, TimeUnit.SECONDS)) {
                                //执行更新操作
                                preOutCall.setBatchStatus(GzxBatchStatusEnum.IN_PROGRESS.getCode());
                                preOutCall.setCompletedCount(recordCountMap.get(preOutCall.getTaskId()).size());
                                if (preOutCall.getTotalCount().equals(preOutCall.getCompletedCount())) {
                                    preOutCall.setBatchStatus(GzxBatchStatusEnum.COMPLETED.getCode());
                                }
                                preOutCall.setUpdateTime(currentTime);
                                preOutCallMapper.updateById(preOutCall);
                            } else {
                                log.warn("无法获取到锁，batchNumber: {}", preOutCall.getBatchNumber());
                            }
                        } catch (Exception e) {
                            log.error("获取分布式锁异常", e);
                        } finally {
                            if (rLock != null && rLock.isHeldByCurrentThread()) {
                                try {
                                    rLock.unlock();
                                } catch (Exception e) {
                                    log.error("释放锁异常", e);
                                }
                            }
                        }
                    }
                }
                //删除多余的话单
                Set<String> callIdSet = new HashSet<>();
                List<Long> deleteIdList = htCallRecordList.stream()
                        .filter(rowData -> !callIdSet.add(rowData.getCallId()))
                        .map(GzxHtCallRecord::getId)
                        .toList();
                htCallRecordMapper.deleteBatchIds(deleteIdList);
            }
        } catch (Exception e) {

        } finally {
            TenantContext.enableTenantFilter();
            TenantContext.clear();
        }
    }

    /**
     * 复制预测外呼明细
     *
     * @param preOutCallAddBo
     */
    @Override
    @DSTransactional(rollbackFor = Exception.class)
    public String copyPreOutCallDetail(GzxPreOutCallAddBo preOutCallAddBo) {
        GzxPreOutSaveBo preOutSaveBo = new GzxPreOutSaveBo();
        BeanUtils.copyProperties(preOutCallAddBo, preOutSaveBo);
        //查询预测外呼明细
        List<GzxPreOutCallDetailVo> preOutCallDetailVoList = preOutCallDetailMapper.selectVoList(Wrappers.<GzxPreOutCallDetail>lambdaQuery()
                .eq(GzxPreOutCallDetail::getBatchNumber, preOutCallAddBo.getBatchNumber())
                .and(preOutCallAddBo.getFilterType() != null && preOutCallAddBo.getFilterType() == 1, lqw ->
                        lqw.isNull(GzxPreOutCallDetail::getCallDuration).or().eq(GzxPreOutCallDetail::getCallDuration, 0))
                .isNull(preOutCallAddBo.getFilterType() != null && preOutCallAddBo.getFilterType() == 2, GzxPreOutCallDetail::getCallDuration)
                .orderByDesc(GzxPreOutCallDetail::getId));
        preOutSaveBo.setPreOutCallDetailVoList(preOutCallDetailVoList);
        SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
        preOutSaveBo.setUserId(loginUser.getId());
        preOutSaveBo.setUserName(loginUser.getName());
        preOutSaveBo.setOrgId(loginUser.getOrgId());
        preOutSaveBo.setAgentNum(loginUser.getAccount());
        preOutSaveBo.setIsPhoneAes(0);
        return insertPreOutCall(preOutSaveBo);
    }

    /**
     * 关闭异常任务
     *
     * @param taskId
     */
    @Override
    public void closeErrorTask(String taskId) {
        switchTask(taskId, 2);
    }

    /**
     * 获取预测外呼参数，Key为手机号，val为透传字段
     *
     * @param batchNumber
     * @return
     */
    @Override
    protected Map<String, Long> getCallParams(String batchNumber) {
        //根据批次号查询出手机号明细
        List<GzxPreOutCallDetail> preOutCallDetailList = preOutCallDetailMapper.selectList(Wrappers.<GzxPreOutCallDetail>lambdaQuery()
                .eq(GzxPreOutCallDetail::getBatchNumber, batchNumber)
                .isNull(GzxPreOutCallDetail::getHtId)
                .orderByDesc(GzxPreOutCallDetail::getId));
        return preOutCallDetailList.stream().collect(Collectors.toMap(GzxPreOutCallDetail::getLiaisonPhone, GzxPreOutCallDetail::getId, (v1, v2) -> v1));
    }

    /**
     * 保存任务ID
     *
     * @param taskId
     * @param idList
     */
    @Override
    protected void saveTaskId(String taskId, List<Long> idList) {
        preOutCallDetailMapper.update(Wrappers.<GzxPreOutCallDetail>lambdaUpdate()
                .set(GzxPreOutCallDetail::getTaskId, taskId)
                .in(GzxPreOutCallDetail::getId, idList));
    }

    private GzxPreOutSaveBo buildImport(GzxPreOutCallAddBo preOutCallAddBo, List<GzxPreOutCallDetailVo> preOutCallDetailVoList, JSONObject userJson) {
        GzxPreOutSaveBo preOutSaveBo = new GzxPreOutSaveBo();
        preOutSaveBo.setPreOutCallDetailVoList(preOutCallDetailVoList);
        preOutSaveBo.setCallNumber(preOutCallAddBo.getCallNumber());
        preOutSaveBo.setCallName(preOutCallAddBo.getCallName());
        preOutSaveBo.setBatchName(preOutCallAddBo.getBatchName());
        preOutSaveBo.setIsPhoneAes(preOutCallAddBo.getIsPhoneAes());
        preOutSaveBo.setCallThread(preOutCallAddBo.getCallThread());
        if (userJson != null) {
            preOutSaveBo.setUserId(userJson.getStr("id"));
            preOutSaveBo.setUserName(userJson.getStr("name"));
            preOutSaveBo.setOrgId(userJson.getStr("orgId"));
            preOutSaveBo.setAgentNum(userJson.getStr("account"));
        } else {
            SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
            preOutSaveBo.setUserId(loginUser.getId());
            preOutSaveBo.setUserName(loginUser.getName());
            preOutSaveBo.setOrgId(loginUser.getOrgId());
            preOutSaveBo.setAgentNum(loginUser.getAccount());

        }
        return preOutSaveBo;
    }

    private List<GzxPreOutCallDetailVo> excelDeduplicateByLiaisonName(MultipartFile multipartFile) throws IOException {
        List<GzxPreOutCallDetailVo> excelResultList = CommonExcelUtil.importExcel(multipartFile, GzxPreOutCallDetailVo.class);
        if (CollectionUtil.isEmpty(excelResultList)) {
            throw new CommonException("Excel为空");
        }
        return excelResultList.stream()
                .collect(Collectors.toMap(
                        GzxPreOutCallDetailVo::getLiaisonPhone,
                        Function.identity(),
                        (existing, replacement) -> existing
                )).values().stream().toList();
    }

    private String insertPreOutCall(GzxPreOutSaveBo preOutSaveBo) {
        //过滤添加的手机号
        List<String> phoneFilterList = phoneFilterService.selectList(null).stream().map(GzxPhoneFilter::getPhoneNumber).toList();
        //过滤投诉用户
        List<String> complaintPhoneList = complaintUserService.listAll(new GzxComplaintUserQueryBo()).stream().map(GzxComplaintUserVo::getMobile).toList();
        Map<String, String> resultMap = verifyPreOutCallDetail(preOutSaveBo, phoneFilterList, complaintPhoneList);
        List<GzxPreOutCallDetailVo> preOutCallDetailVoList = JSON.parseArray(resultMap.get("result"), GzxPreOutCallDetailVo.class);
        if (CollectionUtil.isNotEmpty(preOutCallDetailVoList)) {
            String batchNumber = "POC" + CommonBatchNumberUtil.getBatchNumber(BatchNumberConstant.PRE_BATCH_NUMBER);
            //保存预测外呼批次数据
            insertBatch(batchNumber, preOutSaveBo, preOutCallDetailVoList.size());
            //预测外呼明细数据
            insertBatchDetail(preOutCallDetailVoList, batchNumber, preOutSaveBo.getCallNumber(), preOutSaveBo.getUserId(), preOutSaveBo.getIsPhoneAes());
            return resultMap.get("msg") + "共成功" + preOutCallDetailVoList.size() + "条\n";
        } else {
            return resultMap.get("msg");
        }
    }

    private Map<String, String> verifyPreOutCallDetail(GzxPreOutSaveBo preOutSaveBo, List<String> phoneFilterList, List<String> complaintPhoneList) {
        String resultKey1 = "msg";
        String resultKey2 = "result";
        List<GzxPreOutCallDetailVo> preOutCallDetailVoList = preOutSaveBo.getPreOutCallDetailVoList();
        if (CollectionUtil.isEmpty(preOutCallDetailVoList)) {
            return Map.of(resultKey1, preOutSaveBo.getUserName() + "预测外呼上传名单为空\n", resultKey2, JSON.toJSONString(List.of()));
        }
        if (preOutCallDetailVoList.size() > preOutCallCount) {
            return Map.of(resultKey1, preOutSaveBo.getUserName() + "每次上传外呼数据不得超过" + preOutCallCount + "\n", resultKey2, JSON.toJSONString(List.of()));
        }
        StringBuilder sb = new StringBuilder(preOutSaveBo.getUserName()).append("->");
        Integer isPhoneAes = preOutSaveBo.getIsPhoneAes();
        List<GzxPreOutCallDetailVo> resultList = new ArrayList<>();
        int rowNum = 1;
        for (GzxPreOutCallDetailVo preOutCallDetailVo : preOutSaveBo.getPreOutCallDetailVoList()) {
            String liaisonPhone = preOutCallDetailVo.getLiaisonPhone();
            String phoneMsg = verifyLiaisonFormat(liaisonPhone, isPhoneAes);
            if (StringUtils.isNotBlank(phoneMsg)) {
                sb.append("第").append(rowNum).append("行，客户姓名：").append(preOutCallDetailVo.getLiaisonName()).append("，").append(preOutCallDetailVo.getLiaisonPhone()).append(phoneMsg).append("\n");
                continue;
            }
            String customerInfo = preOutCallDetailVo.getCustomerInfo();
            if (StringUtils.isNotBlank(customerInfo) && customerInfo.length() > 100) {
                sb.append("第").append(rowNum).append("行自定义客户信息字段超长").append("\n");
                continue;
            }
            if (isPhoneAes == 1) {
                liaisonPhone = CommonCryptogramUtil.doSm1AesDecrypt(liaisonPhone);
            }
            //校验投诉用户和过滤用户
            String phoneMsgByDb = verifyLiaisonByDb(liaisonPhone, phoneFilterList, complaintPhoneList);
            if (StringUtils.isNotBlank(phoneMsgByDb)) {
                sb.append("第").append(rowNum).append("行：客户姓名：").append(preOutCallDetailVo.getLiaisonName()).append("，").append(preOutCallDetailVo.getLiaisonPhone()).append(phoneMsgByDb).append("\n");
                continue;
            }
            resultList.add(preOutCallDetailVo);
        }
        return Map.of(resultKey1, sb.toString(), resultKey2, JSON.toJSONString(resultList));
    }

    private String verifyLiaisonFormat(String liaisonPhone, Integer isPhoneAes) {
        if (StringUtils.isBlank(liaisonPhone)) {
            return "手机号为空";
        }
        if (isPhoneAes == 1 && CommonStringFormatUtil.isValidPhone(liaisonPhone)) {
            return "手机号非密文";
        }
        if (isPhoneAes == 0 && !CommonStringFormatUtil.isValidPhone(liaisonPhone)) {
            return "手机号非手机号，解析出错";
        }
        return null;
    }

    private String verifyLiaisonByDb(String liaisonPhone, List<String> phoneFilterList, List<String> complaintPhoneList) {
        //校验投诉用户
        if (complaintPhoneList.contains(liaisonPhone)) {
            return "为敏感用户";
        }
        //手机号过滤
        if (phoneFilterList.contains(liaisonPhone)) {
            return "为过滤用户";
        }
        return null;
    }

    private void insertBatch(String batchNumber, GzxPreOutSaveBo preOutSaveBo, int totalSize) {
        GzxPreOutCall preOutCall = new GzxPreOutCall();
        preOutCall.setBatchNumber(batchNumber);
        preOutCall.setBatchName(preOutSaveBo.getBatchName());
        preOutCall.setCaller(preOutSaveBo.getCallNumber());
        preOutCall.setCallName(preOutSaveBo.getCallName());
        preOutCall.setCallThread(preOutSaveBo.getCallThread());
        preOutCall.setTotalCount(totalSize);
        preOutCall.setTenantId(StpLoginUserUtil.getLoginUser().getTenantId());
        preOutCall.setCreateUser(preOutSaveBo.getUserId());
        preOutCall.setCreateName(preOutSaveBo.getUserName());
        preOutCall.setAgentNum(preOutSaveBo.getAgentNum());
        preOutCall.setCreateDept(Long.valueOf(preOutSaveBo.getOrgId()));
        preOutCallMapper.insert(preOutCall);
    }

    private void insertBatchDetail(List<GzxPreOutCallDetailVo> preOutCallDetailVoList, String batchNumber, String callNumber, String userId, Integer isPhoneAes) {
        Date currentTime = new Date();
        String tenantId = StpLoginUserUtil.getLoginUser().getTenantId();
        List<GzxPreOutCallDetail> preOutCallDetailList = new ArrayList<>();
        for (GzxPreOutCallDetailVo preOutCallDetailVo : preOutCallDetailVoList) {
            GzxPreOutCallDetail preOutCallDetail = new GzxPreOutCallDetail();
            preOutCallDetail.setBatchNumber(batchNumber);
            preOutCallDetail.setCaller(callNumber);
            if (StringUtils.isBlank(preOutCallDetailVo.getCustomerInfo()) || !preOutCallDetailVo.getCustomerInfo().startsWith("姓名")) {
                preOutCallDetail.setCustomerInfo("姓名：" + preOutCallDetailVo.getLiaisonName() + "，电话：" + CommonSensitiveUtil.sensitivePhone(isPhoneAes == 1 ? CommonCryptogramUtil.doSm1AesDecrypt(preOutCallDetailVo.getLiaisonPhone()) : preOutCallDetailVo.getLiaisonPhone()) + (StringUtils.isNotBlank(preOutCallDetailVo.getCustomerInfo()) ? "，信息：" + preOutCallDetailVo.getCustomerInfo() : ""));
            } else {
                preOutCallDetail.setCustomerInfo(preOutCallDetailVo.getCustomerInfo());
            }
            preOutCallDetail.setLiaisonName(preOutCallDetailVo.getLiaisonName());
            if (isPhoneAes == 0) {
                preOutCallDetail.setLiaisonPhone(CommonCryptogramUtil.doSm1AesEncrypt(preOutCallDetailVo.getLiaisonPhone()));
            } else {
                preOutCallDetail.setLiaisonPhone(preOutCallDetailVo.getLiaisonPhone());
            }
            preOutCallDetail.setPartnerBusinessId(preOutCallDetailVo.getPartnerBusinessId());
            preOutCallDetail.setTenantId(tenantId);
            preOutCallDetail.setCreateUser(userId);
            preOutCallDetail.setCreateTime(currentTime);
            preOutCallDetailList.add(preOutCallDetail);
        }
        setPreBusiness(preOutCallDetailList, userId);
        List<List<GzxPreOutCallDetail>> batchList = Lists.partition(preOutCallDetailList, IntConstant.VALUE_350);
        batchList.forEach(preOutCallDetailMapper::insertBatchDetail);
        CommonSleepUtil.sleep(IntConstant.VALUE_100);
    }


    private void setPreBusiness(List<GzxPreOutCallDetail> preOutCallDetailList, String userId) {
        //优先根据甲方系统主键匹配案件
        Set<String> parentBusinessIdSet = preOutCallDetailList.stream().map(GzxPreOutCallDetail::getPartnerBusinessId).filter(Objects::nonNull).filter(id -> !id.isEmpty()).collect(Collectors.toSet());
        if (CollectionUtil.isNotEmpty(parentBusinessIdSet)) {
            //根据甲方系统主键查询案件详情
            List<GzxCaseDetail> caseDetailList = caseDbService.selectCaseDetailList(Wrappers.<GzxCaseDetail>lambdaQuery()
                    .select(GzxCaseDetail::getBatchNumber, GzxCaseDetail::getDebtorCardId, GzxCaseDetail::getPartnerBusinessId)
                    .in(GzxCaseDetail::getPartnerBusinessId, parentBusinessIdSet));
            log.debug("甲方系统主键：{}", parentBusinessIdSet);
            log.debug("案件详情{}", caseDetailList);
            //批量查询案件
            Map<String, Set<String>> batchEncryptedDebtorCardIds = caseDetailList.stream()
                    .collect(Collectors.groupingBy(
                            GzxCaseDetail::getBatchNumber,
                            Collectors.mapping(
                                    caseDetail -> CommonCryptogramUtil.doSm1AesEncrypt(caseDetail.getDebtorCardId()),
                                    Collectors.toSet())));
            log.debug("分批{}", batchEncryptedDebtorCardIds);
            if (CollectionUtil.isNotEmpty(batchEncryptedDebtorCardIds)) {
                List<GzxCase> caseList = caseDbService.listCasesByBatchAndDebtorIds(
                        batchEncryptedDebtorCardIds.entrySet().stream()
                                .map(e -> new CaseQueryParam(e.getKey(), e.getValue()))
                                .toList());

                log.debug("查询案件主表{}", caseList);
                //案件转Map
                Map<String, Long> caseMap = caseList.stream()
                        .filter(caseObj -> StringUtils.isNotEmpty(caseObj.getDebtorCardId()))
                        .collect(Collectors.toMap(
                                GzxCase::getDebtorCardId,
                                GzxCase::getId,
                                Math::max,
                                HashMap::new
                        ));
                log.debug("案件转Map:{}", caseMap);
                Set<String> validDebtorCardIds = caseMap.keySet();
                Map<String, Long> caseDetailMap = caseDetailList.stream()
                        .parallel()
                        .filter(detail ->
                                StringUtils.isNotEmpty(detail.getPartnerBusinessId()) &&
                                        StringUtils.isNotEmpty(CommonCryptogramUtil.doSm1AesEncrypt(detail.getDebtorCardId())) &&
                                        validDebtorCardIds.contains(CommonCryptogramUtil.doSm1AesEncrypt(detail.getDebtorCardId())))
                        .collect(Collectors.toMap(
                                GzxCaseDetail::getPartnerBusinessId,
                                detail -> caseMap.get(CommonCryptogramUtil.doSm1AesEncrypt(detail.getDebtorCardId())),
                                (existingId, newId) -> newId,
                                LinkedHashMap::new
                        ));
                log.debug("案件详情Map:{}", caseDetailMap);
                preOutCallDetailList.forEach(detail -> {
                    log.debug("获取案件ID:{}", detail.getPartnerBusinessId());
                    log.debug("获取案件ID结果:{}", caseDetailMap.containsKey(detail.getPartnerBusinessId()));
                    if (caseDetailMap.containsKey(detail.getPartnerBusinessId())) {
                        detail.setCaseId(caseDetailMap.get(detail.getPartnerBusinessId()));
                    }
                });
            }
        }
        //查询出CaseId为null，且手机号非空的数据
        List<String> liaisonPhoneList = preOutCallDetailList.stream()
                .filter(d -> d.getCaseId() == null)
                .map(GzxPreOutCallDetail::getLiaisonPhone)
                .filter(StringUtils::isNotEmpty)
                .distinct()
                .toList();
        if (CollectionUtil.isNotEmpty(liaisonPhoneList)) {
            //根据手机号查询三方联系人
            List<GzxDebtorLiaison> debtorLiaisonList = debtorLiaisonService.selectList(Wrappers.<GzxDebtorLiaison>lambdaQuery().in(GzxDebtorLiaison::getLiaisonPhone, liaisonPhoneList));
            //加密身份证号
            Set<String> debtorCardIdSet = debtorLiaisonList.stream()
                    .map(GzxDebtorLiaison::getDebtorCardId)
                    .map(CommonCryptogramUtil::doSm1AesEncrypt)
                    .collect(Collectors.toSet());
            if (CollectionUtil.isNotEmpty(debtorCardIdSet)) {
                //根据身份证号查询案件，身份证号相同，保留最新的案件
                Map<String, Long> caseMap = caseDbService.selectCaseList(
                                Wrappers.<GzxCase>lambdaQuery()
                                        .in(GzxCase::getDebtorCardId, debtorCardIdSet)
                                        .eq(GzxCase::getMediatorId, userId))
                        .stream()
                        .filter(caseObj -> caseObj.getDebtorCardId() != null)
                        .collect(Collectors.toMap(
                                GzxCase::getDebtorCardId,
                                GzxCase::getId,
                                Math::max,
                                HashMap::new));
                //联系人信息转Map，手机号为Key，案件ID为Value
                Map<String, Long> liaisonMap = debtorLiaisonList.stream()
                        .filter(liaison -> caseMap.containsKey(liaison.getDebtorCardId()))
                        .collect(Collectors.toMap(
                                GzxDebtorLiaison::getLiaisonPhone,
                                liaison -> caseMap.get(liaison.getDebtorCardId()),
                                Math::max,
                                HashMap::new));
                //填充案件信息，根据手机号匹配案件
                preOutCallDetailList.stream()
                        .filter(d -> d.getCaseId() == null)
                        .forEach(detail -> {
                            if (liaisonMap.containsKey(CommonCryptogramUtil.doSm1AesDecrypt(detail.getLiaisonPhone()))) {
                                detail.setCaseId(liaisonMap.get(CommonCryptogramUtil.doSm1AesDecrypt(detail.getLiaisonPhone())));
                            }
                        });
            }
        }
    }
}
